package com.gitee.wsl.func.filter.bloomfilter

import com.gitee.wsl.ext.base.Arrays.copyOf
import com.gitee.wsl.ext.base.Arrays.fill
import com.gitee.wsl.func.filter.bloomfilter.api.BitMapExtractor
import com.gitee.wsl.func.filter.bloomfilter.api.BloomFilter
import com.gitee.wsl.func.filter.bloomfilter.api.BloomFilter.Companion.SPARSE
import com.gitee.wsl.func.filter.bloomfilter.api.Hasher
import com.gitee.wsl.func.filter.bloomfilter.api.IndexExtractor
import com.gitee.wsl.func.filter.bloomfilter.bean.BitMaps
import com.gitee.wsl.func.filter.bloomfilter.bean.BitMaps.getLongIndex
import com.gitee.wsl.func.filter.bloomfilter.bean.SetOperations
import com.gitee.wsl.func.filter.bloomfilter.bean.Shape
import com.gitee.wsl.func.filter.bloomfilter.predicate.CountingLongPredicate
import com.gitee.wsl.func.predicate.IntPredicate
import com.gitee.wsl.func.predicate.LongBiPredicate
import com.gitee.wsl.func.predicate.LongPredicate
import com.gitee.wsl.func.predicate.Predicate
import com.gitee.wsl.text.format.format


/**
 * A bloom filter using an array of bit maps to track enabled bits. This is a standard implementation and should work well for most Bloom filters.
 *
 * @since 4.5.0-M1
 */
class SimpleBloomFilter : BloomFilter<SimpleBloomFilter> {
    /**
     * The array of bit map longs that defines this Bloom filter. Will be null if the filter is empty.
     */
    private val bitMap: LongArray

    /**
     * The Shape of this Bloom filter.
     */
    override val shape: Shape

    /**
     * The cardinality of this Bloom filter.
     */
    private var cardinality: Int

    /**
     * Creates an empty instance.
     *
     * @param shape The shape for the filter.
     */
    constructor(shape: Shape) {
        this.shape = shape
        this.bitMap = BitMaps.newBitMap(shape)
        this.cardinality = 0
    }

    /**
     * Copy constructor for `copy()` use.
     *
     * @param source
     */
    private constructor(source: SimpleBloomFilter) {
        this.shape = source.shape
        this.bitMap = source.bitMap.clone()
        this.cardinality = source.cardinality
    }

     override fun asBitMapArray(): LongArray {
        return copyOf(bitMap, bitMap.size)
    }

     override fun cardinality(): Int {
        // Lazy evaluation with caching
        var c = cardinality
        if (c < 0) {
            c = SetOperations.cardinality(this)
            cardinality = c
        }
        return c
    }

     override fun characteristics(): Int {
        return 0
    }

     override fun clear() {
        fill(bitMap, 0L)
        cardinality = 0
    }

     override fun contains(indexExtractor: IndexExtractor): Boolean {
        return indexExtractor.processIndices(Predicate { idx: Int -> BitMaps.contains(bitMap, idx) })
    }

    /**
     * Creates a new instance of this [SimpleBloomFilter] with the same properties as the current one.
     *
     * @return a copy of this [SimpleBloomFilter].
     */
     override fun copy(): SimpleBloomFilter {
        return SimpleBloomFilter(this)
    }

    override fun isEmpty(): Boolean = cardinality == 0 || processBitMaps(LongPredicate { y -> y == 0L })

    override fun merge(bitMapExtractor: BitMapExtractor): Boolean {
        try {
            val idx = IntArray(1)
            bitMapExtractor.processBitMaps(Predicate { value ->
                val index = idx[0]++
                bitMap[index] = bitMap[index] or value
                true
            })
            // idx[0] will be limit+1 so decrement it
            idx[0]--
            val idxLimit = getLongIndex(shape.numberOfBits)
            if (idxLimit == idx[0]) {
                val excess = bitMap[idxLimit] shr shape.numberOfBits
                require(excess == 0L) {
                    String.format(
                        "BitMapExtractor set a bit higher than the limit for the shape: %s",
                        shape.numberOfBits
                    )
                }
            }
            cardinality = -1
        } catch (e: IndexOutOfBoundsException) {
            throw IllegalArgumentException(
                String.format(
                    "BitMapExtractor should send at most %s maps",
                    bitMap.size
                ), e
            )
        }
        return true
    }

     override fun merge(other: BloomFilter<*>): Boolean {
        if ((other.characteristics() and SPARSE) != 0) {
            merge(other as IndexExtractor)
        } else {
            merge(other as BitMapExtractor)
        }
        return true
    }

     override fun merge(hasher: Hasher): Boolean {
        return merge(hasher.indices(shape))
    }

     override fun merge(indexExtractor: IndexExtractor): Boolean {
        indexExtractor.processIndices(Predicate { idx ->
            require(!(idx < 0 || idx >= shape.numberOfBits)) {
                String.format(
                    "IndexExtractor should only send values in the range[0,%s)",
                    shape.numberOfBits
                )
            }
            BitMaps.set(bitMap, idx)
            true
        })
        cardinality = -1
        return true
    }

     override fun processBitMapPairs(other: BitMapExtractor, func: LongBiPredicate): Boolean {
        val p = CountingLongPredicate(bitMap, func)
        return other.processBitMaps(p) && p.processRemaining()
    }

     override fun processBitMaps(predicate: LongPredicate): Boolean {
        for (l in bitMap) {
            if (!predicate.test(l)) {
                return false
            }
        }
        return true
    }

     override fun processIndices(predicate: IntPredicate): Boolean {
        return IndexExtractor.fromBitMapExtractor(this).processIndices(predicate)
    }
}
